// ##########################################################################
// #                                                                        #
// #                              CLOUDCOMPARE                              #
// #                                                                        #
// #  This program is free software; you can redistribute it and/or modify  #
// #  it under the terms of the GNU General Public License as published by  #
// #  the Free Software Foundation; version 2 or later of the License.      #
// #                                                                        #
// #  This program is distributed in the hope that it will be useful,       #
// #  but WITHOUT ANY WARRANTY; without even the implied warranty of        #
// #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          #
// #  GNU General Public License for more details.                          #
// #                                                                        #
// #          COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI)             #
// #                                                                        #
// ##########################################################################

#ifndef CC_COLOR_TYPES_HEADER
#define CC_COLOR_TYPES_HEADER

// Local
#include "qCC_db.h"

// Qt
#include <QColor>

//! Default color components type (R,G and B)
using ColorCompType = unsigned char;

//! Colors namespace
namespace ccColor
{
	//! Max value of a single color component (default type)
	constexpr ColorCompType MAX  = 255;
	constexpr int           MAXi = 255;

	//! RGB color structure
	template <typename Type>
	class RgbTpl
	{
	  public:
		//! 3-tuple as a union
		union
		{
			struct
			{
				Type r, g, b;
			};
			Type rgb[3];
		};

		//! Default constructor
		/** Inits color to (0,0,0).
		 **/
		constexpr inline RgbTpl()
		    : r(0)
		    , g(0)
		    , b(0)
		{
		}

		//! Constructor from a triplet of r,g,b values
		explicit constexpr inline RgbTpl(Type red, Type green, Type blue)
		    : r(red)
		    , g(green)
		    , b(blue)
		{
		}

		//! Constructor from an array of 3 values
		explicit constexpr inline RgbTpl(const Type col[3])
		    : r(col[0])
		    , g(col[1])
		    , b(col[2])
		{
		}

		//! Comparison operator
		inline bool operator!=(const RgbTpl<Type>& t) const
		{
			return (r != t.r || g != t.g || b != t.b);
		}
	};

	//! 3 components, float type
	using Rgbf = RgbTpl<float>;
	//! 3 components, unsigned byte type
	using Rgbub = RgbTpl<unsigned char>;
	//! 3 components, default type
	using Rgb = RgbTpl<ColorCompType>;

	//! RGBA color structure
	template <class Type>
	class RgbaTpl
	{
	  public:
		// 4-tuple values as a union
		union
		{
			struct
			{
				Type r, g, b, a;
			};
			Type rgba[4];
		};

		//! Default constructor
		/** Inits color to (0,0,0,0).
		 **/
		constexpr inline RgbaTpl()
		    : r(0)
		    , g(0)
		    , b(0)
		    , a(0)
		{
		}

		//! Constructor from a triplet of r,g,b values and a transparency value
		explicit constexpr inline RgbaTpl(Type red, Type green, Type blue, Type alpha)
		    : r(red)
		    , g(green)
		    , b(blue)
		    , a(alpha)
		{
		}

		//! RgbaTpl from an array of 4 values
		explicit constexpr inline RgbaTpl(const Type col[4])
		    : r(col[0])
		    , g(col[1])
		    , b(col[2])
		    , a(col[3])
		{
		}
		//! RgbaTpl from an array of 3 values and a transparency value
		explicit constexpr inline RgbaTpl(const Type col[3], Type alpha)
		    : r(col[0])
		    , g(col[1])
		    , b(col[2])
		    , a(alpha)
		{
		}

		//! Copy constructor
		inline RgbaTpl(const RgbTpl<Type>& c, Type alpha)
		    : r(c.r)
		    , g(c.g)
		    , b(c.b)
		    , a(alpha)
		{
		}

		//! Cast operator
		inline operator RgbTpl<Type>() const
		{
			return RgbTpl<Type>(rgba);
		}
		//! Cast operator (const version)
		// inline operator const Type*() const { return rgba; }

		//! Comparison operator
		inline bool operator!=(const RgbaTpl<Type>& t) const
		{
			return (r != t.r || g != t.g || b != t.b || a != t.a);
		}
	};

	//! 4 components, float type
	using Rgbaf = RgbaTpl<float>;
	//! 4 components, unsigned byte type
	using Rgbaub = RgbaTpl<unsigned char>;
	//! 4 components, default type
	using Rgba = RgbaTpl<ColorCompType>;

	// Predefined colors (default type)
	constexpr Rgb whiteRGB(MAX, MAX, MAX);
	constexpr Rgb lightGreyRGB(static_cast<ColorCompType>(MAXi * 8 / 10), static_cast<ColorCompType>(MAXi * 8 / 10), static_cast<ColorCompType>(MAXi * 8 / 10));
	constexpr Rgb darkGreyRGB(MAX / 2, MAX / 2, MAX / 2);
	constexpr Rgb redRGB(MAX, 0, 0);
	constexpr Rgb greenRGB(0, MAX, 0);
	constexpr Rgb blueRGB(0, 0, MAX);
	constexpr Rgb blueCCRGB(0, static_cast<ColorCompType>(MAXi * 7 / 10), MAX);
	constexpr Rgb darkBlueRGB(0, 0, MAX / 2);
	constexpr Rgb magentaRGB(MAX, 0, MAX);
	constexpr Rgb cyanRGB(0, MAX, MAX);
	constexpr Rgb orangeRGB(MAX, MAX / 2, 0);
	constexpr Rgb blackRGB(0, 0, 0);
	constexpr Rgb yellowRGB(MAX, MAX, 0);

	// Predefined colors (default type)
	constexpr Rgba white(whiteRGB.r, whiteRGB.g, whiteRGB.b, MAX);
	constexpr Rgba lightGrey(lightGreyRGB.r, lightGreyRGB.g, lightGreyRGB.b, MAX);
	constexpr Rgba darkGrey(darkGreyRGB.r, darkGreyRGB.g, darkGreyRGB.b, MAX);
	constexpr Rgba red(redRGB.r, redRGB.g, redRGB.b, MAX);
	constexpr Rgba green(greenRGB.r, greenRGB.g, greenRGB.b, MAX);
	constexpr Rgba blue(blueRGB.r, blueRGB.g, blueRGB.b, MAX);
	constexpr Rgba blueCC(blueCCRGB.r, blueCCRGB.g, blueCCRGB.b, MAX);
	constexpr Rgba darkBlue(darkBlueRGB.r, darkBlueRGB.g, darkBlueRGB.b, MAX);
	constexpr Rgba magenta(magentaRGB.r, magentaRGB.g, magentaRGB.b, MAX);
	constexpr Rgba cyan(cyanRGB.r, cyanRGB.g, cyanRGB.b, MAX);
	constexpr Rgba orange(orangeRGB.r, orangeRGB.g, orangeRGB.b, MAX);
	constexpr Rgba black(blackRGB.r, blackRGB.g, blackRGB.b, MAX);
	constexpr Rgba yellow(yellowRGB.r, yellowRGB.g, yellowRGB.b, MAX);

	// Predefined materials (float)
	constexpr Rgbaf bright(1.00f, 1.00f, 1.00f, 1.00f);
	constexpr Rgbaf lighter(0.83f, 0.83f, 0.83f, 1.00f);
	constexpr Rgbaf light(0.66f, 0.66f, 0.66f, 1.00f);
	constexpr Rgbaf middle(0.50f, 0.50f, 0.50f, 1.00f);
	constexpr Rgbaf dark(0.34f, 0.34f, 0.34f, 1.00f);
	constexpr Rgbaf darker(0.17f, 0.17f, 0.17f, 1.00f);
	constexpr Rgbaf darkest(0.08f, 0.08f, 0.08f, 1.00f);
	constexpr Rgbaf night(0.00f, 0.00f, 0.00f, 1.00F);
	constexpr Rgbaf defaultMeshFrontDiff(0.00f, 0.90f, 0.27f, 1.00f);
	constexpr Rgbaf defaultMeshBackDiff(0.27f, 0.90f, 0.90f, 1.00f);

	// Default foreground color (unsigned byte)
	constexpr Rgbub defaultBkgColor(10, 102, 151);             // dark blue
	constexpr Rgba  defaultColor(MAX, MAX, MAX, MAX);          // white
	constexpr Rgba  defaultLabelBkgColor(MAX, MAX, MAX, MAX);  // white
	constexpr Rgba  defaultLabelMarkerColor(MAX, 0, MAX, MAX); // magenta

	//! Colors generator
	class Generator
	{
	  public:
		//! Generates a random color
		QCC_DB_LIB_API static Rgb Random(bool lightOnly = true);
	};

	//! Color space conversion
	class Convert
	{
	  public:
		//! Converts a HSL color to RGB color space
		/** \param H [out] hue [0;360[
		    \param S [out] saturation [0;1]
		    \param L [out] light [0;1]
		    \return RGB color (unsigned byte)
		**/
		static Rgb hsl2rgb(float H, float S, float L)
		{
			H /= 360;
			float q = L < 0.5f ? L * (1.0f + S) : L + S - L * S;
			float p = 2 * L - q;

			float r = hue2rgb(p, q, H + 1.0f / 3.0f);
			float g = hue2rgb(p, q, H);
			float b = hue2rgb(p, q, H - 1.0f / 3.0f);

			return Rgb(static_cast<ColorCompType>(r * ccColor::MAX),
			           static_cast<ColorCompType>(g * ccColor::MAX),
			           static_cast<ColorCompType>(b * ccColor::MAX));
		}

		//! Converts a HSV color to RGB color space
		/** \param H [out] hue [0;360[
		    \param S [out] saturation [0;1]
		    \param V [out] value [0;1]
		    \return RGB color (unsigned byte)
		**/
		QCC_DB_LIB_API static Rgb hsv2rgb(float H, float S, float V);

	  private:
		//! Method used by hsl2rgb
		static float hue2rgb(float m1, float m2, float hue)
		{
			if (hue < 0)
				hue += 1.0f;
			else if (hue > 1.0f)
				hue -= 1.0f;

			if (6 * hue < 1.0f)
				return m1 + (m2 - m1) * hue * 6;
			else if (2 * hue < 1.0f)
				return m2;
			else if (3 * hue < 2.0f)
				return m1 + (m2 - m1) * (4.0f - hue * 6);
			else
				return m1;
		}
	};

	//! Conversion from Rgbf
	inline Rgb FromRgbfToRgb(const Rgbf& color)
	{
		return Rgb(static_cast<ColorCompType>(color.r * MAX),
		           static_cast<ColorCompType>(color.g * MAX),
		           static_cast<ColorCompType>(color.b * MAX));
	}

	//! Conversion from Rgbaf to Rgb
	inline Rgb FromRgbafToRgb(const Rgbaf& color)
	{
		return Rgb(static_cast<ColorCompType>(color.r * MAX),
		           static_cast<ColorCompType>(color.g * MAX),
		           static_cast<ColorCompType>(color.b * MAX));
	}

	//! Conversion from Rgb to Rgba
	inline Rgba FromRgbToRgba(const Rgb& color)
	{
		return Rgba(color, MAX);
	}

	//! Conversion from Rgbaf to Rgba
	inline Rgba FromRgbafToRgba(const Rgbaf& color)
	{
		return Rgba(static_cast<ColorCompType>(color.r * MAX),
		            static_cast<ColorCompType>(color.g * MAX),
		            static_cast<ColorCompType>(color.b * MAX),
		            static_cast<ColorCompType>(color.a * MAX));
	}

	//! Conversion from QRgb
	inline Rgb FromQRgb(QRgb qColor)
	{
		return Rgb(static_cast<unsigned char>(qRed(qColor)),
		           static_cast<unsigned char>(qGreen(qColor)),
		           static_cast<unsigned char>(qBlue(qColor)));
	}

	//! Conversion from QRgb'a'
	inline Rgba FromQRgba(QRgb qColor)
	{
		return Rgba(static_cast<unsigned char>(qRed(qColor)),
		            static_cast<unsigned char>(qGreen(qColor)),
		            static_cast<unsigned char>(qBlue(qColor)),
		            static_cast<unsigned char>(qAlpha(qColor)));
	}

	//! Conversion from QColor
	inline Rgb FromQColor(const QColor& qColor)
	{
		return Rgb(static_cast<unsigned char>(qColor.red()),
		           static_cast<unsigned char>(qColor.green()),
		           static_cast<unsigned char>(qColor.blue()));
	}

	//! Conversion from QColor'a'
	inline Rgba FromQColora(const QColor& qColor)
	{
		return Rgba(static_cast<unsigned char>(qColor.red()),
		            static_cast<unsigned char>(qColor.green()),
		            static_cast<unsigned char>(qColor.blue()),
		            static_cast<unsigned char>(qColor.alpha()));
	}

	//! Conversion from QColor (floating point)
	inline Rgbf FromQColorf(const QColor& qColor)
	{
		return Rgbf(static_cast<float>(qColor.redF()),
		            static_cast<float>(qColor.greenF()),
		            static_cast<float>(qColor.blueF()));
	}

	//! Conversion from QColor'a' (floating point)
	inline Rgbaf FromQColoraf(const QColor& qColor)
	{
		return Rgbaf(static_cast<float>(qColor.redF()),
		             static_cast<float>(qColor.greenF()),
		             static_cast<float>(qColor.blueF()),
		             static_cast<float>(qColor.alphaF()));
	}
}; // namespace ccColor

#endif // CC_COLOR_TYPES_HEADER
